package com.mybatis.boost.generator.plugin;

import com.google.common.base.Splitter;
import com.mybatis.boost.generator.plugin.adapter.MapperPluginAdapter;
import com.mybatis.boost.generator.xml.XmlElement;
import com.mybatis.boost.mapper.util.sql.SqlUtil;
import org.apache.ibatis.type.JdbcType;
import org.mybatis.generator.api.IntrospectedColumn;
import org.mybatis.generator.api.IntrospectedTable;
import org.mybatis.generator.api.dom.java.Field;
import org.mybatis.generator.api.dom.java.FullyQualifiedJavaType;
import org.mybatis.generator.api.dom.java.Interface;
import org.mybatis.generator.api.dom.java.TopLevelClass;
import org.mybatis.generator.api.dom.xml.Attribute;
import org.mybatis.generator.api.dom.xml.Document;
import org.mybatis.generator.api.dom.xml.TextElement;
import org.mybatis.generator.config.JavaClientGeneratorConfiguration;
import org.mybatis.generator.internal.util.StringUtility;

import java.io.File;
import java.util.List;
import java.util.Properties;
import java.util.Set;
import java.util.StringTokenizer;

import static com.google.common.collect.ImmutableMap.of;
import static com.google.common.collect.Sets.newHashSet;
import static com.mybatis.boost.generator.util.GeneratorUtil.addOrUpdateAnnotation;
import static com.mybatis.boost.mapper.model.MapperConstants.GENERATED_JDBC;

/**
 * @author jearton
 * @since 2017/2/4
 */
public class MapperPlugin extends MapperPluginAdapter {
    // 通用mapper接口，可指定多个
    private Set<String> mappers = newHashSet();

    @Override
    public boolean validate(List<String> warnings) {
        return true;
    }

    @Override
    public void setProperties(Properties properties) {
        String mappers = properties.getProperty("mappers");
        if (StringUtility.stringHasValue(mappers)) {
            this.mappers = newHashSet(Splitter.on(",").split(mappers));
        } else {
            throw new RuntimeException("Mapper插件缺少必要的mappers属性!");
        }
    }

    /**
     * 生成Mapper接口
     */
    @Override
    public boolean clientGenerated(Interface interfaze, TopLevelClass topLevelClass, IntrospectedTable introspectedTable) {
        File directory = _getClientDirectory();
        String fileName = interfaze.getType().getShortNameWithoutTypeArguments() + ".java";
        if (new File(directory, fileName).exists()) {
            return false;
        }

        //代码生成信息
        String annotation = "@Generated(\n" +
                "\t\tvalue = \"mybatis-extend\",\n" +
                "\t\tcomments = \"This interface is generated by mybatis-extend, don't delete it.\"\n" +
                ")";
        interfaze.addImportedType(new FullyQualifiedJavaType("javax.annotation.Generated"));
        interfaze.addAnnotation(annotation);

        //@Repository
        interfaze.addImportedType(new FullyQualifiedJavaType("org.apache.ibatis.annotations"));
        interfaze.addAnnotation("@Mapper");

        //通用Mapper父接口
        FullyQualifiedJavaType entityType = new FullyQualifiedJavaType(introspectedTable.getBaseRecordType());
        interfaze.addImportedType(entityType);
        for (String mapper : mappers) {
            interfaze.addImportedType(new FullyQualifiedJavaType(mapper));
            interfaze.addSuperInterface(new FullyQualifiedJavaType(mapper + "<" + entityType.getShortName() + ">"));
        }
        return true;
    }

    /**
     * 生成基础实体类
     */
    @Override
    public boolean modelBaseRecordClassGenerated(TopLevelClass topLevelClass, IntrospectedTable introspectedTable) {
        _processEntityClass(topLevelClass, introspectedTable);
        return true;
    }

    /**
     * 生成实体类注解KEY对象
     */
    @Override
    public boolean modelPrimaryKeyClassGenerated(TopLevelClass topLevelClass, IntrospectedTable introspectedTable) {
        _processEntityClass(topLevelClass, introspectedTable);
        return true;
    }

    /**
     * 生成带BLOB字段的对象
     */
    @Override
    public boolean modelRecordWithBLOBsClassGenerated(TopLevelClass topLevelClass, IntrospectedTable introspectedTable) {
        _processEntityClass(topLevelClass, introspectedTable);
        return true;
    }

    /**
     * 生成实体的属性
     */
    @Override
    public boolean modelFieldGenerated(Field field, TopLevelClass topLevelClass, IntrospectedColumn introspectedColumn, IntrospectedTable introspectedTable, ModelClassType modelClassType) {
        //@Id
        if (introspectedTable.getPrimaryKeyColumns().stream().anyMatch(introspectedColumn::equals)) {
            addOrUpdateAnnotation(field, "@Id");
        }

        //@Column
        String column = introspectedColumn.getActualColumnName();
        addOrUpdateAnnotation(field, "@Column", of("name", "\"" + _getDelimiterName(column) + "\""));

        //@GeneratedValue/@SequenceGenerator
        if (introspectedColumn.isIdentity()) {
            if (introspectedTable.getTableConfiguration().getGeneratedKey().getRuntimeSqlStatement().equals(GENERATED_JDBC)) {
                addOrUpdateAnnotation(field, "@GeneratedValue", of("generator", "\"" + GENERATED_JDBC + "\""));
            } else {
                addOrUpdateAnnotation(field, "@GeneratedValue", of("strategy", "GenerationType.IDENTITY"));
            }
        } else if (introspectedColumn.isSequenceColumn()) {
            String sequenceName = introspectedTable.getTableConfiguration().getGeneratedKey().getRuntimeSqlStatement();
            addOrUpdateAnnotation(field, "@SequenceGenerator", of("name", "\"\"", "sequenceName", "\"" + sequenceName + "\""));
        }

        //validation
        if (!introspectedColumn.isNullable()) {
            topLevelClass.addImportedType("javax.validation.constraints.NotNull");
            field.addAnnotation("@NotNull");
        }
        int jdbcType = introspectedColumn.getJdbcType();
        if (JdbcType.CHAR.TYPE_CODE == jdbcType || JdbcType.VARCHAR.TYPE_CODE == jdbcType) {
            int length = introspectedColumn.getLength();
            topLevelClass.addImportedType("javax.validation.constraints.Size");
            field.addAnnotation("@Size(max = " + length + ")");
        }
        return true;
    }

    @Override
    public boolean sqlMapDocumentGenerated(Document document, IntrospectedTable introspectedTable) {
        String table = introspectedTable.getFullyQualifiedTable().getIntrospectedTableName();
        XmlElement xmlElement = new XmlElement("sql");
        xmlElement.addAttribute(new Attribute("id", "Base_Table"));
        xmlElement.addElement(new TextElement("<!--"));
        xmlElement.addElement(new TextElement("  WARNING - @mbg.generated"));
        xmlElement.addElement(new TextElement("-->"));
        xmlElement.addElement(new TextElement(SqlUtil.addDelimiter(table)));
        document.getRootElement().addElement(xmlElement);
        return true;
    }

    /**
     * 处理实体类的@Table注解
     */
    private void _processEntityClass(TopLevelClass topLevelClass, IntrospectedTable introspectedTable) {
        String table = introspectedTable.getFullyQualifiedTableNameAtRuntime();
        if (StringUtility.stringContainsSpace(table)) {
            throw new IllegalArgumentException("数据库表名不能带有空格");
        }

        //引入JPA注解
        topLevelClass.addImportedType("javax.persistence.*");

        //@Table
        topLevelClass.addAnnotation("@Table(name = \"" + _getDelimiterName(table) + "\")");
    }

    private String _getDelimiterName(String name) {
        return context.getBeginningDelimiter() + name + context.getEndingDelimiter();
    }

    private File _getClientDirectory() {
        JavaClientGeneratorConfiguration configuration = getContext().getJavaClientGeneratorConfiguration();
        String targetProject = configuration.getTargetProject();
        String targetPackage = configuration.getTargetPackage();

        File project = new File(targetProject);
        if (!project.isDirectory()) {
            throw new RuntimeException("targetProject is not a directory");
        }

        StringBuilder sb = new StringBuilder();
        StringTokenizer st = new StringTokenizer(targetPackage, "."); //$NON-NLS-1$
        while (st.hasMoreTokens()) {
            sb.append(st.nextToken());
            sb.append(File.separatorChar);
        }

        File directory = new File(project, sb.toString());
        if (!directory.isDirectory()) {
            if (!directory.mkdirs()) {
                throw new RuntimeException("can not make directory :" + directory.getAbsolutePath());
            }
        }
        return directory;
    }
}
